package util;

import play.libs.WS;
import play.libs.WS.HttpResponse;
import play.libs.WS.WSRequest;

import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.javadocmd.simplelatlng.LatLng;

public class GoogleGeocode {

	private String region = null;
	private String forcedType = null;

	public GoogleGeocode() {

	}

	public GoogleGeocode(String region, String forcedType) {
		this.region = region;
		this.forcedType = forcedType;
	}

	public void setRegion(String region) {
		this.region = region;
	}

	public void setForcedType(String type) {
		this.forcedType = type;
	}

	public String geocodeRaw(String address) throws GeocodeException {
		WSRequest request = WS
				.url("http://maps.googleapis.com/maps/api/geocode/json");
		request.setParameter("address", address);
		request.setParameter("sensor", "false");
		if (region != null) {
			request.setParameter("region", region);
		}

		HttpResponse response = request.get();
		return new GsonBuilder().setPrettyPrinting().create().toJson(
				response.getJson());
	}

	public LatLng geocode(String address) throws GeocodeException {
		WSRequest request = WS
				.url("http://maps.googleapis.com/maps/api/geocode/json");
		request.setParameter("address", address);
		request.setParameter("sensor", "false");
		if (region != null) {
			request.setParameter("region", region);
		}

		HttpResponse response = request.get();

		if (response.getStatus() == 200) {
			try {
				JsonObject json = response.getJson().getAsJsonObject();
				String status = json.get("status").getAsString();
				if ("OVER_QUERY_LIMIT".equalsIgnoreCase(status)) {
					throw new GeocodeQueryLimitException();
				}
				if ("REQUEST_DENIED".equalsIgnoreCase(status)
						|| "INVALID_REQUEST".equalsIgnoreCase(status)) {
					throw new GeocodeException();
				}
				if ("ZERO_RESULTS".equalsIgnoreCase(status)) {
					return null;
				}
				if ("OK".equalsIgnoreCase(status)) {
					JsonObject firstResult = json.get("results").getAsJsonArray()
							.get(0).getAsJsonObject();

					if (forcedType != null && !isTypeMatch(firstResult)) {
						return null;
					}
					if (region != null && !isRegionMatch(firstResult)) {
						return null;
					}

					JsonObject location = firstResult.get("geometry")
							.getAsJsonObject().get("location").getAsJsonObject();
					double lat = location.get("lat").getAsDouble();
					double lng = location.get("lng").getAsDouble();
					return new LatLng(lat, lng);
				}
			} catch (NullPointerException e) {
				// Fall through to throw exception.
			}
		}
		throw new GeocodeException();
	}

	private boolean isTypeMatch(JsonObject result) {
		try {
			JsonArray types = result.get("types").getAsJsonArray();
			for (JsonElement e : types) {
				String t = e.getAsString();
				if (t.equalsIgnoreCase(forcedType)) {
					return true;
				}
			}
		} catch (NullPointerException e) {
			// Fall through to return false.
		}
		return false;
	}

	private boolean isRegionMatch(JsonObject result) {
		try {
			JsonArray addressComps = result.get("address_components")
					.getAsJsonArray();
			for (JsonElement e : addressComps) {
				JsonObject component = e.getAsJsonObject();
				String shortName = component.get("short_name").getAsString();
				JsonArray types = component.get("types").getAsJsonArray();
				for (JsonElement e2 : types) {
					String t = e2.getAsString();
					if (t.equalsIgnoreCase("country")
							&& shortName.equalsIgnoreCase(region)) {
						return true;
					}
				}
			}
		} catch (NullPointerException e) {
			// Fall through to return false.
		}
		return false;
	}

	public static class GeocodeException extends Exception {

	}

	public static class GeocodeQueryLimitException extends GeocodeException {

	}
}
